/*!
 * # Semantic UI 2.4.1 - API
 * http://github.com/semantic-org/semantic-ui/
 *
 *
 * Released under the MIT license
 * http://opensource.org/licenses/MIT
 *
 */

;(function ($, window, document, undefined) {

    'use strict';

    var
        window = (typeof window != 'undefined' && window.Math == Math)
            ? window
            : (typeof self != 'undefined' && self.Math == Math)
                ? self
                : Function('return this')()
    ;

    $.api = $.fn.api = function (parameters) {

        var
            // use window context if none specified
            $allModules = $.isFunction(this)
                ? $(window)
                : $(this),
            moduleSelector = $allModules.selector || '',
            time = new Date().getTime(),
            performance = [],

            query = arguments[0],
            methodInvoked = (typeof query == 'string'),
            queryArguments = [].slice.call(arguments, 1),

            returnedValue
        ;

        $allModules
            .each(function () {
                var
                    settings = ($.isPlainObject(parameters))
                        ? $.extend(true, {}, $.fn.api.settings, parameters)
                        : $.extend({}, $.fn.api.settings),

                    // internal aliases
                    namespace = settings.namespace,
                    metadata = settings.metadata,
                    selector = settings.selector,
                    error = settings.error,
                    className = settings.className,

                    // define namespaces for modules
                    eventNamespace = '.' + namespace,
                    moduleNamespace = 'module-' + namespace,

                    // element that creates request
                    $module = $(this),
                    $form = $module.closest(selector.form),

                    // context used for state
                    $context = (settings.stateContext)
                        ? $(settings.stateContext)
                        : $module,

                    // request details
                    ajaxSettings,
                    requestSettings,
                    url,
                    data,
                    requestStartTime,

                    // standard module
                    element = this,
                    context = $context[0],
                    instance = $module.data(moduleNamespace),
                    module
                ;

                module = {

                    initialize: function () {
                        if (!methodInvoked) {
                            module.bind.events();
                        }
                        module.instantiate();
                    },

                    instantiate: function () {
                        module.verbose('Storing instance of module', module);
                        instance = module;
                        $module
                            .data(moduleNamespace, instance)
                        ;
                    },

                    destroy: function () {
                        module.verbose('Destroying previous module for', element);
                        $module
                            .removeData(moduleNamespace)
                            .off(eventNamespace)
                        ;
                    },

                    bind: {
                        events: function () {
                            var
                                triggerEvent = module.get.event()
                            ;
                            if (triggerEvent) {
                                module.verbose('Attaching API events to element', triggerEvent);
                                $module
                                    .on(triggerEvent + eventNamespace, module.event.trigger)
                                ;
                            }
                            else if (settings.on == 'now') {
                                module.debug('Querying API endpoint immediately');
                                module.query();
                            }
                        }
                    },

                    decode: {
                        json: function (response) {
                            if (response !== undefined && typeof response == 'string') {
                                try {
                                    response = JSON.parse(response);
                                }
                                catch (e) {
                                    // isnt json string
                                }
                            }
                            return response;
                        }
                    },

                    read: {
                        cachedResponse: function (url) {
                            var
                                response
                            ;
                            if (window.Storage === undefined) {
                                module.error(error.noStorage);
                                return;
                            }
                            response = sessionStorage.getItem(url);
                            module.debug('Using cached response', url, response);
                            response = module.decode.json(response);
                            return response;
                        }
                    },
                    write: {
                        cachedResponse: function (url, response) {
                            if (response && response === '') {
                                module.debug('Response empty, not caching', response);
                                return;
                            }
                            if (window.Storage === undefined) {
                                module.error(error.noStorage);
                                return;
                            }
                            if ($.isPlainObject(response)) {
                                response = JSON.stringify(response);
                            }
                            sessionStorage.setItem(url, response);
                            module.verbose('Storing cached response for url', url, response);
                        }
                    },

                    query: function () {

                        if (module.is.disabled()) {
                            module.debug('Element is disabled API request aborted');
                            return;
                        }

                        if (module.is.loading()) {
                            if (settings.interruptRequests) {
                                module.debug('Interrupting previous request');
                                module.abort();
                            }
                            else {
                                module.debug('Cancelling request, previous request is still pending');
                                return;
                            }
                        }

                        // pass element metadata to url (value, text)
                        if (settings.defaultData) {
                            $.extend(true, settings.urlData, module.get.defaultData());
                        }

                        // Add form content
                        if (settings.serializeForm) {
                            settings.data = module.add.formData(settings.data);
                        }

                        // call beforesend and get any settings changes
                        requestSettings = module.get.settings();

                        // check if before send cancelled request
                        if (requestSettings === false) {
                            module.cancelled = true;
                            module.error(error.beforeSend);
                            return;
                        }
                        else {
                            module.cancelled = false;
                        }

                        // get url
                        url = module.get.templatedURL();

                        if (!url && !module.is.mocked()) {
                            module.error(error.missingURL);
                            return;
                        }

                        // replace variables
                        url = module.add.urlData(url);
                        // missing url parameters
                        if (!url && !module.is.mocked()) {
                            return;
                        }

                        requestSettings.url = settings.base + url;

                        // look for jQuery ajax parameters in settings
                        ajaxSettings = $.extend(true, {}, settings, {
                            type: settings.method || settings.type,
                            data: data,
                            url: settings.base + url,
                            beforeSend: settings.beforeXHR,
                            success: function () {
                            },
                            failure: function () {
                            },
                            complete: function () {
                            }
                        });

                        module.debug('Querying URL', ajaxSettings.url);
                        module.verbose('Using AJAX settings', ajaxSettings);
                        if (settings.cache === 'local' && module.read.cachedResponse(url)) {
                            module.debug('Response returned from local cache');
                            module.request = module.create.request();
                            module.request.resolveWith(context, [module.read.cachedResponse(url)]);
                            return;
                        }

                        if (!settings.throttle) {
                            module.debug('Sending request', data, ajaxSettings.method);
                            module.send.request();
                        }
                        else {
                            if (!settings.throttleFirstRequest && !module.timer) {
                                module.debug('Sending request', data, ajaxSettings.method);
                                module.send.request();
                                module.timer = setTimeout(function () {
                                }, settings.throttle);
                            }
                            else {
                                module.debug('Throttling request', settings.throttle);
                                clearTimeout(module.timer);
                                module.timer = setTimeout(function () {
                                    if (module.timer) {
                                        delete module.timer;
                                    }
                                    module.debug('Sending throttled request', data, ajaxSettings.method);
                                    module.send.request();
                                }, settings.throttle);
                            }
                        }

                    },

                    should: {
                        removeError: function () {
                            return (settings.hideError === true || (settings.hideError === 'auto' && !module.is.form()));
                        }
                    },

                    is: {
                        disabled: function () {
                            return ($module.filter(selector.disabled).length > 0);
                        },
                        expectingJSON: function () {
                            return settings.dataType === 'json' || settings.dataType === 'jsonp';
                        },
                        form: function () {
                            return $module.is('form') || $context.is('form');
                        },
                        mocked: function () {
                            return (settings.mockResponse || settings.mockResponseAsync || settings.response || settings.responseAsync);
                        },
                        input: function () {
                            return $module.is('input');
                        },
                        loading: function () {
                            return (module.request)
                                ? (module.request.state() == 'pending')
                                : false
                                ;
                        },
                        abortedRequest: function (xhr) {
                            if (xhr && xhr.readyState !== undefined && xhr.readyState === 0) {
                                module.verbose('XHR request determined to be aborted');
                                return true;
                            }
                            else {
                                module.verbose('XHR request was not aborted');
                                return false;
                            }
                        },
                        validResponse: function (response) {
                            if ((!module.is.expectingJSON()) || !$.isFunction(settings.successTest)) {
                                module.verbose('Response is not JSON, skipping validation', settings.successTest, response);
                                return true;
                            }
                            module.debug('Checking JSON returned success', settings.successTest, response);
                            if (settings.successTest(response)) {
                                module.debug('Response passed success test', response);
                                return true;
                            }
                            else {
                                module.debug('Response failed success test', response);
                                return false;
                            }
                        }
                    },

                    was: {
                        cancelled: function () {
                            return (module.cancelled || false);
                        },
                        succesful: function () {
                            return (module.request && module.request.state() == 'resolved');
                        },
                        failure: function () {
                            return (module.request && module.request.state() == 'rejected');
                        },
                        complete: function () {
                            return (module.request && (module.request.state() == 'resolved' || module.request.state() == 'rejected'));
                        }
                    },

                    add: {
                        urlData: function (url, urlData) {
                            var
                                requiredVariables,
                                optionalVariables
                            ;
                            if (url) {
                                requiredVariables = url.match(settings.regExp.required);
                                optionalVariables = url.match(settings.regExp.optional);
                                urlData = urlData || settings.urlData;
                                if (requiredVariables) {
                                    module.debug('Looking for required URL variables', requiredVariables);
                                    $.each(requiredVariables, function (index, templatedString) {
                                        var
                                            // allow legacy {$var} style
                                            variable = (templatedString.indexOf('$') !== -1)
                                                ? templatedString.substr(2, templatedString.length - 3)
                                                : templatedString.substr(1, templatedString.length - 2),
                                            value = ($.isPlainObject(urlData) && urlData[variable] !== undefined)
                                                ? urlData[variable]
                                                : ($module.data(variable) !== undefined)
                                                    ? $module.data(variable)
                                                    : ($context.data(variable) !== undefined)
                                                        ? $context.data(variable)
                                                        : urlData[variable]
                                        ;
                                        // remove value
                                        if (value === undefined) {
                                            module.error(error.requiredParameter, variable, url);
                                            url = false;
                                            return false;
                                        }
                                        else {
                                            module.verbose('Found required variable', variable, value);
                                            value = (settings.encodeParameters)
                                                ? module.get.urlEncodedValue(value)
                                                : value
                                            ;
                                            url = url.replace(templatedString, value);
                                        }
                                    });
                                }
                                if (optionalVariables) {
                                    module.debug('Looking for optional URL variables', requiredVariables);
                                    $.each(optionalVariables, function (index, templatedString) {
                                        var
                                            // allow legacy {/$var} style
                                            variable = (templatedString.indexOf('$') !== -1)
                                                ? templatedString.substr(3, templatedString.length - 4)
                                                : templatedString.substr(2, templatedString.length - 3),
                                            value = ($.isPlainObject(urlData) && urlData[variable] !== undefined)
                                                ? urlData[variable]
                                                : ($module.data(variable) !== undefined)
                                                    ? $module.data(variable)
                                                    : ($context.data(variable) !== undefined)
                                                        ? $context.data(variable)
                                                        : urlData[variable]
                                        ;
                                        // optional replacement
                                        if (value !== undefined) {
                                            module.verbose('Optional variable Found', variable, value);
                                            url = url.replace(templatedString, value);
                                        }
                                        else {
                                            module.verbose('Optional variable not found', variable);
                                            // remove preceding slash if set
                                            if (url.indexOf('/' + templatedString) !== -1) {
                                                url = url.replace('/' + templatedString, '');
                                            }
                                            else {
                                                url = url.replace(templatedString, '');
                                            }
                                        }
                                    });
                                }
                            }
                            return url;
                        },
                        formData: function (data) {
                            var
                                canSerialize = ($.fn.serializeObject !== undefined),
                                formData = (canSerialize)
                                    ? $form.serializeObject()
                                    : $form.serialize(),
                                hasOtherData
                            ;
                            data = data || settings.data;
                            hasOtherData = $.isPlainObject(data);

                            if (hasOtherData) {
                                if (canSerialize) {
                                    module.debug('Extending existing data with form data', data, formData);
                                    data = $.extend(true, {}, data, formData);
                                }
                                else {
                                    module.error(error.missingSerialize);
                                    module.debug('Cant extend data. Replacing data with form data', data, formData);
                                    data = formData;
                                }
                            }
                            else {
                                module.debug('Adding form data', formData);
                                data = formData;
                            }
                            return data;
                        }
                    },

                    send: {
                        request: function () {
                            module.set.loading();
                            module.request = module.create.request();
                            if (module.is.mocked()) {
                                module.mockedXHR = module.create.mockedXHR();
                            }
                            else {
                                module.xhr = module.create.xhr();
                            }
                            settings.onRequest.call(context, module.request, module.xhr);
                        }
                    },

                    event: {
                        trigger: function (event) {
                            module.query();
                            if (event.type == 'submit' || event.type == 'click') {
                                event.preventDefault();
                            }
                        },
                        xhr: {
                            always: function () {
                                // nothing special
                            },
                            done: function (response, textStatus, xhr) {
                                var
                                    context = this,
                                    elapsedTime = (new Date().getTime() - requestStartTime),
                                    timeLeft = (settings.loadingDuration - elapsedTime),
                                    translatedResponse = ($.isFunction(settings.onResponse))
                                        ? module.is.expectingJSON()
                                            ? settings.onResponse.call(context, $.extend(true, {}, response))
                                            : settings.onResponse.call(context, response)
                                        : false
                                ;
                                timeLeft = (timeLeft > 0)
                                    ? timeLeft
                                    : 0
                                ;
                                if (translatedResponse) {
                                    module.debug('Modified API response in onResponse callback', settings.onResponse, translatedResponse, response);
                                    response = translatedResponse;
                                }
                                if (timeLeft > 0) {
                                    module.debug('Response completed early delaying state change by', timeLeft);
                                }
                                setTimeout(function () {
                                    if (module.is.validResponse(response)) {
                                        module.request.resolveWith(context, [response, xhr]);
                                    }
                                    else {
                                        module.request.rejectWith(context, [xhr, 'invalid']);
                                    }
                                }, timeLeft);
                            },
                            fail: function (xhr, status, httpMessage) {
                                var
                                    context = this,
                                    elapsedTime = (new Date().getTime() - requestStartTime),
                                    timeLeft = (settings.loadingDuration - elapsedTime)
                                ;
                                timeLeft = (timeLeft > 0)
                                    ? timeLeft
                                    : 0
                                ;
                                if (timeLeft > 0) {
                                    module.debug('Response completed early delaying state change by', timeLeft);
                                }
                                setTimeout(function () {
                                    if (module.is.abortedRequest(xhr)) {
                                        module.request.rejectWith(context, [xhr, 'aborted', httpMessage]);
                                    }
                                    else {
                                        module.request.rejectWith(context, [xhr, 'error', status, httpMessage]);
                                    }
                                }, timeLeft);
                            }
                        },
                        request: {
                            done: function (response, xhr) {
                                module.debug('Successful API Response', response);
                                if (settings.cache === 'local' && url) {
                                    module.write.cachedResponse(url, response);
                                    module.debug('Saving server response locally', module.cache);
                                }
                                settings.onSuccess.call(context, response, $module, xhr);
                            },
                            complete: function (firstParameter, secondParameter) {
                                var
                                    xhr,
                                    response
                                ;
                                // have to guess callback parameters based on request success
                                if (module.was.succesful()) {
                                    response = firstParameter;
                                    xhr = secondParameter;
                                }
                                else {
                                    xhr = firstParameter;
                                    response = module.get.responseFromXHR(xhr);
                                }
                                module.remove.loading();
                                settings.onComplete.call(context, response, $module, xhr);
                            },
                            fail: function (xhr, status, httpMessage) {
                                var
                                    // pull response from xhr if available
                                    response = module.get.responseFromXHR(xhr),
                                    errorMessage = module.get.errorFromRequest(response, status, httpMessage)
                                ;
                                if (status == 'aborted') {
                                    module.debug('XHR Aborted (Most likely caused by page navigation or CORS Policy)', status, httpMessage);
                                    settings.onAbort.call(context, status, $module, xhr);
                                    return true;
                                }
                                else if (status == 'invalid') {
                                    module.debug('JSON did not pass success test. A server-side error has most likely occurred', response);
                                }
                                else if (status == 'error') {
                                    if (xhr !== undefined) {
                                        module.debug('XHR produced a server error', status, httpMessage);
                                        // make sure we have an error to display to console
                                        if (xhr.status != 200 && httpMessage !== undefined && httpMessage !== '') {
                                            module.error(error.statusMessage + httpMessage, ajaxSettings.url);
                                        }
                                        settings.onError.call(context, errorMessage, $module, xhr);
                                    }
                                }

                                if (settings.errorDuration && status !== 'aborted') {
                                    module.debug('Adding error state');
                                    module.set.error();
                                    if (module.should.removeError()) {
                                        setTimeout(module.remove.error, settings.errorDuration);
                                    }
                                }
                                module.debug('API Request failed', errorMessage, xhr);
                                settings.onFailure.call(context, response, $module, xhr);
                            }
                        }
                    },

                    create: {

                        request: function () {
                            // api request promise
                            return $.Deferred()
                                .always(module.event.request.complete)
                                .done(module.event.request.done)
                                .fail(module.event.request.fail)
                                ;
                        },

                        mockedXHR: function () {
                            var
                                // xhr does not simulate these properties of xhr but must return them
                                textStatus = false,
                                status = false,
                                httpMessage = false,
                                responder = settings.mockResponse || settings.response,
                                asyncResponder = settings.mockResponseAsync || settings.responseAsync,
                                asyncCallback,
                                response,
                                mockedXHR
                            ;

                            mockedXHR = $.Deferred()
                                .always(module.event.xhr.complete)
                                .done(module.event.xhr.done)
                                .fail(module.event.xhr.fail)
                            ;

                            if (responder) {
                                if ($.isFunction(responder)) {
                                    module.debug('Using specified synchronous callback', responder);
                                    response = responder.call(context, requestSettings);
                                }
                                else {
                                    module.debug('Using settings specified response', responder);
                                    response = responder;
                                }
                                // simulating response
                                mockedXHR.resolveWith(context, [response, textStatus, {responseText: response}]);
                            }
                            else if ($.isFunction(asyncResponder)) {
                                asyncCallback = function (response) {
                                    module.debug('Async callback returned response', response);

                                    if (response) {
                                        mockedXHR.resolveWith(context, [response, textStatus, {responseText: response}]);
                                    }
                                    else {
                                        mockedXHR.rejectWith(context, [{responseText: response}, status, httpMessage]);
                                    }
                                };
                                module.debug('Using specified async response callback', asyncResponder);
                                asyncResponder.call(context, requestSettings, asyncCallback);
                            }
                            return mockedXHR;
                        },

                        xhr: function () {
                            var
                                xhr
                            ;
                            // ajax request promise
                            xhr = $.ajax(ajaxSettings)
                                .always(module.event.xhr.always)
                                .done(module.event.xhr.done)
                                .fail(module.event.xhr.fail)
                            ;
                            module.verbose('Created server request', xhr, ajaxSettings);
                            return xhr;
                        }
                    },

                    set: {
                        error: function () {
                            module.verbose('Adding error state to element', $context);
                            $context.addClass(className.error);
                        },
                        loading: function () {
                            module.verbose('Adding loading state to element', $context);
                            $context.addClass(className.loading);
                            requestStartTime = new Date().getTime();
                        }
                    },

                    remove: {
                        error: function () {
                            module.verbose('Removing error state from element', $context);
                            $context.removeClass(className.error);
                        },
                        loading: function () {
                            module.verbose('Removing loading state from element', $context);
                            $context.removeClass(className.loading);
                        }
                    },

                    get: {
                        responseFromXHR: function (xhr) {
                            return $.isPlainObject(xhr)
                                ? (module.is.expectingJSON())
                                    ? module.decode.json(xhr.responseText)
                                    : xhr.responseText
                                : false
                                ;
                        },
                        errorFromRequest: function (response, status, httpMessage) {
                            return ($.isPlainObject(response) && response.error !== undefined)
                                ? response.error // use json error message
                                : (settings.error[status] !== undefined) // use server error message
                                    ? settings.error[status]
                                    : httpMessage
                                ;
                        },
                        request: function () {
                            return module.request || false;
                        },
                        xhr: function () {
                            return module.xhr || false;
                        },
                        settings: function () {
                            var
                                runSettings
                            ;
                            runSettings = settings.beforeSend.call(context, settings);
                            if (runSettings) {
                                if (runSettings.success !== undefined) {
                                    module.debug('Legacy success callback detected', runSettings);
                                    module.error(error.legacyParameters, runSettings.success);
                                    runSettings.onSuccess = runSettings.success;
                                }
                                if (runSettings.failure !== undefined) {
                                    module.debug('Legacy failure callback detected', runSettings);
                                    module.error(error.legacyParameters, runSettings.failure);
                                    runSettings.onFailure = runSettings.failure;
                                }
                                if (runSettings.complete !== undefined) {
                                    module.debug('Legacy complete callback detected', runSettings);
                                    module.error(error.legacyParameters, runSettings.complete);
                                    runSettings.onComplete = runSettings.complete;
                                }
                            }
                            if (runSettings === undefined) {
                                module.error(error.noReturnedValue);
                            }
                            if (runSettings === false) {
                                return runSettings;
                            }
                            return (runSettings !== undefined)
                                ? $.extend(true, {}, runSettings)
                                : $.extend(true, {}, settings)
                                ;
                        },
                        urlEncodedValue: function (value) {
                            var
                                decodedValue = window.decodeURIComponent(value),
                                encodedValue = window.encodeURIComponent(value),
                                alreadyEncoded = (decodedValue !== value)
                            ;
                            if (alreadyEncoded) {
                                module.debug('URL value is already encoded, avoiding double encoding', value);
                                return value;
                            }
                            module.verbose('Encoding value using encodeURIComponent', value, encodedValue);
                            return encodedValue;
                        },
                        defaultData: function () {
                            var
                                data = {}
                            ;
                            if (!$.isWindow(element)) {
                                if (module.is.input()) {
                                    data.value = $module.val();
                                }
                                else if (module.is.form()) {

                                }
                                else {
                                    data.text = $module.text();
                                }
                            }
                            return data;
                        },
                        event: function () {
                            if ($.isWindow(element) || settings.on == 'now') {
                                module.debug('API called without element, no events attached');
                                return false;
                            }
                            else if (settings.on == 'auto') {
                                if ($module.is('input')) {
                                    return (element.oninput !== undefined)
                                        ? 'input'
                                        : (element.onpropertychange !== undefined)
                                            ? 'propertychange'
                                            : 'keyup'
                                        ;
                                }
                                else if ($module.is('form')) {
                                    return 'submit';
                                }
                                else {
                                    return 'click';
                                }
                            }
                            else {
                                return settings.on;
                            }
                        },
                        templatedURL: function (action) {
                            action = action || $module.data(metadata.action) || settings.action || false;
                            url = $module.data(metadata.url) || settings.url || false;
                            if (url) {
                                module.debug('Using specified url', url);
                                return url;
                            }
                            if (action) {
                                module.debug('Looking up url for action', action, settings.api);
                                if (settings.api[action] === undefined && !module.is.mocked()) {
                                    module.error(error.missingAction, settings.action, settings.api);
                                    return;
                                }
                                url = settings.api[action];
                            }
                            else if (module.is.form()) {
                                url = $module.attr('action') || $context.attr('action') || false;
                                module.debug('No url or action specified, defaulting to form action', url);
                            }
                            return url;
                        }
                    },

                    abort: function () {
                        var
                            xhr = module.get.xhr()
                        ;
                        if (xhr && xhr.state() !== 'resolved') {
                            module.debug('Cancelling API request');
                            xhr.abort();
                        }
                    },

                    // reset state
                    reset: function () {
                        module.remove.error();
                        module.remove.loading();
                    },

                    setting: function (name, value) {
                        module.debug('Changing setting', name, value);
                        if ($.isPlainObject(name)) {
                            $.extend(true, settings, name);
                        }
                        else if (value !== undefined) {
                            if ($.isPlainObject(settings[name])) {
                                $.extend(true, settings[name], value);
                            }
                            else {
                                settings[name] = value;
                            }
                        }
                        else {
                            return settings[name];
                        }
                    },
                    internal: function (name, value) {
                        if ($.isPlainObject(name)) {
                            $.extend(true, module, name);
                        }
                        else if (value !== undefined) {
                            module[name] = value;
                        }
                        else {
                            return module[name];
                        }
                    },
                    debug: function () {
                        if (!settings.silent && settings.debug) {
                            if (settings.performance) {
                                module.performance.log(arguments);
                            }
                            else {
                                module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
                                module.debug.apply(console, arguments);
                            }
                        }
                    },
                    verbose: function () {
                        if (!settings.silent && settings.verbose && settings.debug) {
                            if (settings.performance) {
                                module.performance.log(arguments);
                            }
                            else {
                                module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
                                module.verbose.apply(console, arguments);
                            }
                        }
                    },
                    error: function () {
                        if (!settings.silent) {
                            module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
                            module.error.apply(console, arguments);
                        }
                    },
                    performance: {
                        log: function (message) {
                            var
                                currentTime,
                                executionTime,
                                previousTime
                            ;
                            if (settings.performance) {
                                currentTime = new Date().getTime();
                                previousTime = time || currentTime;
                                executionTime = currentTime - previousTime;
                                time = currentTime;
                                performance.push({
                                    'Name': message[0],
                                    'Arguments': [].slice.call(message, 1) || '',
                                    //'Element'        : element,
                                    'Execution Time': executionTime
                                });
                            }
                            clearTimeout(module.performance.timer);
                            module.performance.timer = setTimeout(module.performance.display, 500);
                        },
                        display: function () {
                            var
                                title = settings.name + ':',
                                totalTime = 0
                            ;
                            time = false;
                            clearTimeout(module.performance.timer);
                            $.each(performance, function (index, data) {
                                totalTime += data['Execution Time'];
                            });
                            title += ' ' + totalTime + 'ms';
                            if (moduleSelector) {
                                title += ' \'' + moduleSelector + '\'';
                            }
                            if ((console.group !== undefined || console.table !== undefined) && performance.length > 0) {
                                console.groupCollapsed(title);
                                if (console.table) {
                                    console.table(performance);
                                }
                                else {
                                    $.each(performance, function (index, data) {
                                        console.log(data['Name'] + ': ' + data['Execution Time'] + 'ms');
                                    });
                                }
                                console.groupEnd();
                            }
                            performance = [];
                        }
                    },
                    invoke: function (query, passedArguments, context) {
                        var
                            object = instance,
                            maxDepth,
                            found,
                            response
                        ;
                        passedArguments = passedArguments || queryArguments;
                        context = element || context;
                        if (typeof query == 'string' && object !== undefined) {
                            query = query.split(/[\. ]/);
                            maxDepth = query.length - 1;
                            $.each(query, function (depth, value) {
                                var camelCaseValue = (depth != maxDepth)
                                    ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
                                    : query
                                ;
                                if ($.isPlainObject(object[camelCaseValue]) && (depth != maxDepth)) {
                                    object = object[camelCaseValue];
                                }
                                else if (object[camelCaseValue] !== undefined) {
                                    found = object[camelCaseValue];
                                    return false;
                                }
                                else if ($.isPlainObject(object[value]) && (depth != maxDepth)) {
                                    object = object[value];
                                }
                                else if (object[value] !== undefined) {
                                    found = object[value];
                                    return false;
                                }
                                else {
                                    module.error(error.method, query);
                                    return false;
                                }
                            });
                        }
                        if ($.isFunction(found)) {
                            response = found.apply(context, passedArguments);
                        }
                        else if (found !== undefined) {
                            response = found;
                        }
                        if ($.isArray(returnedValue)) {
                            returnedValue.push(response);
                        }
                        else if (returnedValue !== undefined) {
                            returnedValue = [returnedValue, response];
                        }
                        else if (response !== undefined) {
                            returnedValue = response;
                        }
                        return found;
                    }
                };

                if (methodInvoked) {
                    if (instance === undefined) {
                        module.initialize();
                    }
                    module.invoke(query);
                }
                else {
                    if (instance !== undefined) {
                        instance.invoke('destroy');
                    }
                    module.initialize();
                }
            })
        ;

        return (returnedValue !== undefined)
            ? returnedValue
            : this
            ;
    };

    $.api.settings = {

        name: 'API',
        namespace: 'api',

        debug: false,
        verbose: false,
        performance: true,

        // object containing all templates endpoints
        api: {},

        // whether to cache responses
        cache: true,

        // whether new requests should abort previous requests
        interruptRequests: true,

        // event binding
        on: 'auto',

        // context for applying state classes
        stateContext: false,

        // duration for loading state
        loadingDuration: 0,

        // whether to hide errors after a period of time
        hideError: 'auto',

        // duration for error state
        errorDuration: 2000,

        // whether parameters should be encoded with encodeURIComponent
        encodeParameters: true,

        // API action to use
        action: false,

        // templated URL to use
        url: false,

        // base URL to apply to all endpoints
        base: '',

        // data that will
        urlData: {},

        // whether to add default data to url data
        defaultData: true,

        // whether to serialize closest form
        serializeForm: false,

        // how long to wait before request should occur
        throttle: 0,

        // whether to throttle first request or only repeated
        throttleFirstRequest: true,

        // standard ajax settings
        method: 'get',
        data: {},
        dataType: 'json',

        // mock response
        mockResponse: false,
        mockResponseAsync: false,

        // aliases for mock
        response: false,
        responseAsync: false,

        // callbacks before request
        beforeSend: function (settings) {
            return settings;
        },
        beforeXHR: function (xhr) {
        },
        onRequest: function (promise, xhr) {
        },

        // after request
        onResponse: false, // function(response) { },

        // response was successful, if JSON passed validation
        onSuccess: function (response, $module) {
        },

        // request finished without aborting
        onComplete: function (response, $module) {
        },

        // failed JSON success test
        onFailure: function (response, $module) {
        },

        // server error
        onError: function (errorMessage, $module) {
        },

        // request aborted
        onAbort: function (errorMessage, $module) {
        },

        successTest: false,

        // errors
        error: {
            beforeSend: 'The before send function has aborted the request',
            error: 'There was an error with your request',
            exitConditions: 'API Request Aborted. Exit conditions met',
            JSONParse: 'JSON could not be parsed during error handling',
            legacyParameters: 'You are using legacy API success callback names',
            method: 'The method you called is not defined',
            missingAction: 'API action used but no url was defined',
            missingSerialize: 'jquery-serialize-object is required to add form data to an existing data object',
            missingURL: 'No URL specified for api event',
            noReturnedValue: 'The beforeSend callback must return a settings object, beforeSend ignored.',
            noStorage: 'Caching responses locally requires session storage',
            parseError: 'There was an error parsing your request',
            requiredParameter: 'Missing a required URL parameter: ',
            statusMessage: 'Server gave an error: ',
            timeout: 'Your request timed out'
        },

        regExp: {
            required: /\{\$*[A-z0-9]+\}/g,
            optional: /\{\/\$*[A-z0-9]+\}/g,
        },

        className: {
            loading: 'loading',
            error: 'error'
        },

        selector: {
            disabled: '.disabled',
            form: 'form'
        },

        metadata: {
            action: 'action',
            url: 'url'
        }
    };


})(jQuery, window, document);
